Skip to content
0

Spring Boot - 参数校验

前言

数据的校验是网站一个不可或缺的功能,前端的 js 校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用 http 工具直接向后端请求一些违法数据,服务端的数据校验也是必要的。

所以我们可以在 Controller 进行参数校验,只有通过了校验,才能进入 Controller 的方法里。

Controller 中方法参数校验示例

使用 @Valid 或 @Validated 注解。

Maven 依赖

  • @Valid:新版本的 springBoot 需要手动引入下面的依赖,老版本只需引入 spring-boot-starter-web 即可,里面集成了 Hibernate-Validator
xml
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.13.Final</version>
</dependency>
  • @Validated:是对 hibernate-validator 的封装,是 spring 提供的校验机制,只要引入 spring-context 依赖即可

实体类字段加上相关注解

java
public class User implements Serializable {

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 200, message = "年龄不合法")
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

	// setter,getter 方法省略......
}
  • @NotBlank:只用于字符串,字符串不能为 null,并且去除两端空白字符后的长度大于 0,例:""" "
  • @NotEmpty:只用于字符串、集合、map、数组,且不能为 null,并且长度或者大小大于 1
  • @NotNull:适用于所有类型,且不能为 null
  • @AssertTrue:被注释的元素必须为 true
  • @AssertFalse:被注释的元素必须为 false
  • @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max,min):被注释的元素的大小必须在指定的范围内
  • @Email:被注释的元素必须是电子邮件地址
  • @Length:被注释的字符串的大小必须在指定的范围内
  • @Range:被注释的元素必须在合适的范围内

注意:如果是 String 类型,使用 @NotBlank 而不是 NotNull 或者 @NotEmpty,如果字符串是 "",两个后者无法判断为空。

@Valid 或 @Validated 注解

在 Controller 方法参数加上 @Valid 或 @Validated 注解

java
@Controller
public class UserController {

    @RequestMapping(path = "validatorUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult validatorUser(@Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 用于获取相应字段上添加的 message 中的内容
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", user);
    }
}

多个参数校验

java
public Objet test(@Validated Object param1, BindingResult result1 ,@Validated Object param2, BindingResult Result2) {
	// ......
}

@Validated 注解特有的功能

@Valid 不具备的功能,而注解 @Validated 注解特有的功能:分组校验。源码分别如下:

java
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

    Class<?>[] value() default {};
}


@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
}

参数分组校验

当一个实体类需要多种验证方式时,例如:对于一个实体类的 id 来说,新增的时候是不需要的,对于更新时是必须的。可以通过 groups 对验证进行分组。

分组接口(空接口)

通过向 groups 分配不同类的 class 对象,达到分组目的

java
public interface UserServiceInsert {
}

public interface UserServiceUpdate {
}

实体类

java
public class UserInfo implements Serializable {

    @Null(message = "新增时id必须为空", groups = {UserServiceInsert.class})
    @NotNull(message = "更新时id不能为空", groups = {UserServiceUpdate.class})
    @NotNull(message = "删除时 id 不允许为空", groups = UserServiceDelete.class)
    private Integer id;

    @NotBlank(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 200, message = "年龄不合法")
    private Integer age;

    public UserInfo(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

	// setter,getter 方法省略......

    public interface UserServiceInsert {
    }

    public interface UserServiceUpdate {
    }

    public interface UserServiceDelete {
    }
}

Controller 控制类

java
@Controller
public class UserController {

    @RequestMapping(path = "insertUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult insertUser(@Validated(value = UserServiceInsert.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", userInfo);
    }

    @RequestMapping(path = "updateUser", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult updateUser(@Validated(value = UserServiceUpdate.class) UserInfo userInfo, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", userInfo);
    }
}
  • @Validated 添加了 groups 属性时,其只会校验实体分组的属性。如只会校验 UserInfo 中的 id 属性,而不会校验 name,age 属性
  • 不加 groups 时,不会校验有 groups 的属性

嵌套校验

@Valid 加在方法参数时,不会自动进行嵌套验证,而是用在需要嵌套验证类内的相应字段上,来配合方法参数上 @Validated@Valid 来进行嵌套验证

实体类

java
public class Teacher implements Serializable {

    @NotEmpty(message = "Teacher 姓名不能为空")
    private String teacher_name;

    @NotNull(message = "Teacher 年龄不能为空")
    @Range(min = 0, max = 200, message = "Teacher 年龄不合法")
    private Integer teacher_age;

    @Valid
    @Size(min = 1, max = 10, message = "列表中的元素数量为1~10")
    private List<Student> students;

    public Teacher(String teacher_name, Integer teacher_age, List<Student> students) {
        this.teacher_name = teacher_name;
        this.teacher_age = teacher_age;
        this.students = students;
    }

	// setter,getter 方法省略......
}


public class Student implements Serializable {

    @Valid
    @NotEmpty(message = "Student 姓名不能为空")
    private String name;

    @Valid
    @NotNull(message = "Student 年龄不能为空")
    @Range(min = 0, max = 200, message = "Student 年龄不合法")
    private Integer age;

    public Student(String name, Integer age) {
        this.age = age;
        this.name = name;
    }

	// setter,getter 方法省略......
}

Controller 控制类

java
@Controller
public class UserController {

    @RequestMapping(path = "nestValid", method = RequestMethod.POST)
    @ResponseBody
    public ResponseResult nestValid(@Validated @RequestBody Teacher teacher, @NotNull BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            String message = bindingResult.getFieldError().getDefaultMessage();
            return new ResponseResult(500, message, null);
        }
        return new ResponseResult(200, "成功", teacher);
    }
}

对象集合校验

如果 Controller 的参数是一个 List 集合的对象,则该对象不会执行校验,如下:

java
@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody List<GenericCategory> genericCategory) {
    GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
    return HttpResult.okOrFail(category);
}

参数是 List<GenericCategory> genericCategory,而 GenericCategory 实体类里的校验是不会起效的,这是 List 导致的,那么如何解决呢?

既然 @Validated 只能对单个实体类校验,那么我们就自定义一个实体类,实现集合的效果,如下:

java
// @Data 和 @NoArgsConstructor 是 Lombok 带有的,自动生成 setter、getter 等方法,生成无参构造器
@Data
@NoArgsConstructor
public class ValidList<E> implements List<E> {

    @Valid
    private List<E> list = new LinkedList<>();

    public ValidList(List<E> paramList) {
        this.list = paramList;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(0);
    }

    @Override
    public Iterator<E> iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return list.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return list.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return list.addAll(index, c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return list.retainAll(c);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        return list.set(index, element);
    }

    @Override
    public void add(int index, E element) {
        list.add(index, element);
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator<E> listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex, toIndex);
    }
}

ValidList 就是一个对 List 的封装类,封装了大部分常用方法,如果没有你需要的方法,重写就行。

使用:

java
@PostMapping("/queryGenericCategoryPages")
public Response queryGenericCategoryPages(@Validated @RequestBody ValidList<GenericCategory> genericCategory) {
    GenericCategory category = genericCategoryService.insertGenericCategory(genericCategory);
    return HttpResult.okOrFail(category);
}

将 List 改成 ValidList 即可。

控制分组校验顺序

@GroupSequence 它是 JSR 标准提供的注解,可以按指定的分组先后顺序进行验证;前面的分组校验不通过,后面的分组校验就不执行。

  • 如:@GroupSequence({One.class, Two.class, Three.class}) 先执行 One 分组校验,然后执行 Two 分组校验。如果 One 分组校验失败了,则不会进行 Two 分组的校验。即必须第一个组校验正确了,才执行第二组校验
java
@Data
@GroupSequence({One.class, Two.class, UserVO.class})
public class UserVO {

    @NotNull(message = "姓名不能为空", groups = {One.class})
    @Size(min = 1, max = 10, message = "姓名的长度在1-10之间", groups = {Two.class})
    private String name;

    @NotNull(message = "年龄不能为空", groups = {One.class})
    @Min(value = 1, message = "年龄不能小于1岁", groups = {Two.class})
    @Max(value = 200, message = "年龄不能大于200岁", groups = {Two.class})
    private Integer age;

    @NotNull(message = "性别不能为空", groups = {One.class})
    @Min(value = 0, message = "性别取值不能小于0", groups = {Two.class})
    @Max(value = 1, message = "性别取值不能大于1", groups = {Two.class})
    private Integer sex;

    public interface One {}

    public interface Two {}

    public interface UserVO {}
}

测试接口

java
@PostMapping("/test/group")
public String userVO(@RequestBody @Validated UserVO form) {
    return "success";
}

@Validated 类和参数区别

@Validated 可以在 Controller 类上添加,也可以在方法的参数里添加,那么两者区别是什么?

  • 作用在方法上,用于校验实体类里的规则
  • 作用在类上,用于校验类的方法参数规则

注意:如果方法的参数是实体类,那么作用在类上,则不会校验,需要同时在方法里添加 @Validated。

那么 @Validated 作用在类上的作用是什么呢?

能够校验基本类型的参数,如:

java
@RestController
@Validated
public class GenericUserController {

    @PostMapping("/updateGenericUserRole")
    public Response updateGenericUserRole(@NotBlank(message = "无效的参数") String username, @NotNull(message = "无效的参数") Integer projectId, @NotBlank(message = "无效的参数") String roleCode) {
        boolean isSuccess = genericUserService.updateGenericUserRole(username, projectId, roleCode);
        if (!isSuccess) {
            return HttpResult.fail("更新角色失败");
        }
        return HttpResult.ok("更新角色成功");
    }
}

此时 usernameprojectIdroleCode 的校验就会生效。

如果参数是实体类,则不会校验规则。

java
@RestController
@Validated
public class GenericUserController {
    @PostMapping("/test/group")
    public String userVO(@RequestBody UserVO form) {
        return "success";
    }
}

UserVO 里的规则不会起效果,所以我们需要单独加上 @Validated

java
@RestController
@Validated
public class GenericUserController {
    @PostMapping("/test/group")
    public String userVO(@Validated @RequestBody UserVO form) {
        return "success";
    }
}

自定义校验注解

业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验注解来满足我们的需求。

注解 1

如: 添加一个用于校验字符串不能包含空格的校验注解 @CanNotHaveBlank。

  1. 自定义校验注解,并且通过 validatedBy 指定了这个注解真正的验证类

    java
    /**
    * 参考系统提供的校验注解可知,message,groups,payload 这 3 个属性必须提供
    * @Constraint(validatedBy = CanNotHaveBlankValidator.class) 指定该注解的校验类
    */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    //指定了当前注解使用哪个校验类来进行校验。
    @Constraint(validatedBy = {CanNotHaveBlankValidator.class})
    public @interface CanNotHaveBlank {
    
        // 默认错误消息
        String message() default "不能包含空格";
    
        // 分组
        Class<?>[] groups() default {};
    
        // 负载
        Class<? extends Payload>[] payload() default {};
    
        // 指定多个时使用
        @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        @interface List {
            CanNotHaveBlank[] value();
        }
    }

注意: message 用于显示错误信息这个字段是必须的,groupspayload 也是必须的。

@Constraint(validatedBy = {HandsomeBoyValidator.class}) 用来指定处理这个注解逻辑的类。

  1. 编写验证类 CanNotHaveBlankValidator

    • 所有的验证类都需要实现 ConstraintValidator<注解类型,校验bean类型> 接口,实现该接口必须指定对应的注解类型以及校验 Bean 的类型,接口包含一个初始化事件方法 initialize,和一个判断是否合法的方法 isValid
    • isValid() 中的 ConstraintValidatorContext 包含了认证中所有的信息,可以利用这个上下文获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作请附上原文出处链接及本声明
    java
    public interface ConstraintValidator<A extends Annotation, T> {
      default void initialize(A constraintAnnotation) { }
      boolean isValid(T var1, ConstraintValidatorContext var2);
    }
    java
    public class CanNotHaveBlankValidator implements ConstraintValidator<CanNotHaveBlank, String> {
        /**
        * 初始化获取注解的值
        * @param constraintAnnotation
        */
        @Override
        public void initialize(CannotHaveBlank constraintAnnotation) {
            ConstraintValidator.super.initialize(constraintAnnotation);
        }
    
        /**
        * 初始化获取注解的值
        * @param value 要校验的字段值
        * @param context 校验上下文
        * @return true 校验通过、false 校验失败
        */
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            // null 时不进行校验
            if (value != null && !value.contains(" ")) {
                // 获取默认提示信息
                String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
                System.out.println("default message :" + defaultConstraintMessageTemplate);
                // 禁用默认提示信息
                context.disableDefaultConstraintViolation();
                // 设置提示语
                context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
                return false;
            }
            return true;
        }
    }

CannotHaveBlank 是自己定义的注解。

  1. 表单数据

    java
    @Data
    public class CustomForm {
    	// 电话号码
        @CanNotHaveBlank
        private String phone;
    }

注解 2

对状态的校验: status: 1 或者 2.

  1. 自定义注解:

    java
    /**
     * 使用方式:
     * @IncludeValid(value = {"0","1"}, message = "状态值必须为 0, 1")
     *
     *
     * 校验值是否为指定的值
     *
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Constraint(validatedBy = IncludeValidatorClass.class)
    public @interface IncludeValid {
        String[] value() default {};
    
        String message() default "flag is not found";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }
  2. 具体的逻辑校验

    java
    /**
     * 校验值是否为指定的值, 处理类
     *
     */
    public class IncludeValidatorClass implements ConstraintValidator<IncludeValid, Integer> {
        private String[] values;
    
        @Override
        public void initialize(IncludeValid constraintAnnotation) {
            this.values = constraintAnnotation.value();
        }
    
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
            boolean isValid = false;
            if (null == value) {
                return true;
            }
            for (int i = 0; i < values.length; i++) {
                if (values[i].equals(String.valueOf(value))) {
                    isValid = true;
                    break;
                }
            }
            return isValid;
        }
    }
  3. 使用方式

    java
    @NotEmpty(message = "用户名不能为空")
    @ApiModelProperty("用户名")
    private String name;
    
    // 自定义的校验
    @IncludeValid(value = {"1", "2"}, message = "用户的状态不正确")
    private Integer status;

注解 3

手机号码验证

java
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {

	boolean required() default true;

	String message() default "手机号格式不正确";

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};

}

逻辑校验

java
public class MobileValidator implements ConstraintValidator<IsMobile, String>{

	@Override
	public void initialize(IsMobile constraintAnnotation) {
		ConstraintValidator.super.initialize(constraintAnnotation);
	}

	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		// 关于手机号的验证偷个懒 不为空且长度不是 11 位的字符串 就被认为是非法
		if(value != null && value.length() != 11) {
			System.out.println("手机号非法");
			return false;
		}
		return true;
	}
}

使用

java
@Data
public class User {

    // ...

	@IsMobile(message = "手机号格式非法")
	private String mobile;
}

自定义分组校验@GroupSequenceProvider

@GroupSequence只能在类中事先定义校验分组的顺序。

  • 如遇到这种需求: 当 type 值为 A,paramA 值必传。type 值为 B,paramB 值必须传,单独使用分组校验和控制分组校验顺序都无法满足需求。需要使用 @GroupSequenceProvider

定义校验类

java
@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroupForm {
    // 类型
    @Pattern(regexp = "[A|B]", message = "类型不必须为 A|B")
    private String type;

    //参数 A
    @NotEmpty(message = "参数A不能为空", groups = {WhenTypeIsA.class})
    private String paramA;

    // 参数 B
    @NotEmpty(message = "参数B不能为空", groups = {WhenTypeIsB.class})
    private String paramB;

    // 分组 A
    public interface WhenTypeIsA { }
    // 分组 B
    public interface WhenTypeIsB { }
}

实现 DefaultGroupSequenceProvider 接口,编写分组校验逻辑

java
public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> {
    @Override
    public List<Class<?>> getValidationGroups(CustomGroupForm form) {
        List<Class<?>> defaultGroupSequence = new ArrayList<>();
        // 默认分组
        defaultGroupSequence.add(CustomGroupForm.class);

        // 如果类型值为 A 使用 A 分组 WhenTypeIsA
        if (form != null && "A".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class);
        }
        // 如果类型值为 B 使用 B 分组 WhenTypeIsB
        if (form != null && "B".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class);
        }
        // 返回分组
        return defaultGroupSequence;
    }
}

手动校验

在某些场景下需要我们手动校验 bean,用校验器对需要被校验的 bean 发起 validate 获得校验结果

  • 既可以使用 Hibernate Validation 提供 Validator,也可以使用 Spring ValidationValidator

使用 Hibernate Validation 提供 Validator

java
public class ValidationTest {
    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.setUsername(null);
        foo.setPassword(null);
        foo.setUserType("");

		// 构建 Validator
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
		// 使用 Validator 校验 bean
        Set<ConstraintViolation<Foo>> set = validator.validate(foo);
        for (ConstraintViolation<Foo> constraintViolation : set) {
            System.out.println(constraintViolation.getMessage());
        }
    }

    @Data
    public static class Foo {

        @NotNull(message = "username不能为空")
        private String username;

        @NotNull(message = "password不能为空")
        private String password;

        @NotBlank(message = "userType不能为空")
        private String userType;
    }
}

使用 Spring ValidationValidator

也可以使用配置类初始化 LocalValidatorFactoryBean,然后从 Spring 容器中获取

java
@Component
@Configuration
public class GlobalWebConfig {

 	@Bean
	public Validator validator() {
   		return new LocalValidatorFactoryBean();
	}
}

如果使用 springBootLocalValidatorFactoryBean 已经成为了 Validator 的默认实现,使用时只需要自动注入即可

java
@Autowired
Validator globalValidator;

工具类 ValidatorUtils

java
/**
 *  为什么要使用这个工具类呢?
 *   1、controller方法中不用加入BindingResult参数
 *   2、controller方法中需要校验的参数也不需要加入@Valid或者@Validated注解
 * <p>
 *  具体使用
 * 在controller方法或者全局拦截校验器中调用 ValidatorUtils.validateResultProcess(需要校验的Bean) 直接获取校验的结果。
 *
 **/
@Component
public class ValidatorUtils implements ApplicationContextAware {
    //jackson的对象映射类
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static Validator validator;

    /*
     * 校验bean并返回所有验证失败信息
     * @param obj 当前校验对象
     * @param groups 当前校验的组,非必传,不传按照默认分组校验
     * @return 如: Optional[[{"propertyPath":"Foo.password","message":"password为NULL"},{"propertyPath":"Foo.userType","message":"userType为BLANK"}]]
     * @throws ServiceException
     */
    public static Optional<String> validateResultProcess(Object obj, Class<?>... groups) throws ServiceException {
        // 用验证器执行验证,返回一个验证失败的set集合
        Set<ConstraintViolation<Object>> results = validator.validate(obj,groups);

        // 判断是否为空,空:说明验证通过,否则就验证失败
        if (CollectionUtils.isEmpty(results)) {
            return Optional.empty();
        }

        List<ErrorMessage> errorMessages = results.stream()
                //将results转换成 List<ErrorMessage>返回
                .map(result -> {
            try {
                List<ErrorMessage> childErrorMessages = objectMapper.readValue(result.getMessage(), new TypeReference<List<ErrorMessage>>() {
                });
                return childErrorMessages;
            } catch (Exception e) {
                ErrorMessage errorMessage = new ErrorMessage();
                errorMessage.setPropertyPath(String.format("%s.%s", result.getRootBeanClass().getSimpleName(), result.getPropertyPath().toString()));
                errorMessage.setMessage(result.getMessage());
                return Arrays.asList(errorMessage);
            }
        })
                //合并 map操作转换成的多个 List<ErrorMessage>为一个
                .flatMap(errorMessageList -> errorMessageList.stream())
                .collect(Collectors.toList());

        try {
            return Optional.of(objectMapper.writeValueAsString(errorMessages));
        } catch (JsonProcessingException e) {
            throw new ServiceException("JsonProcessingException " + e.getMessage());
        }
    }

    /**
     * 校验bean校验失败抛出自定义异常 ServiceException
     * @param obj 当前校验对象
     * @param groups 当前校验的组,非必传,不传按照默认分组校验
     * @throws ServiceException
     */
    public static void validateResultProcessWithException(Object obj, Class<?>... groups) throws ServiceException {
        Optional<String> validateResult = ValidatorUtils.validateResultProcess(obj,groups);
        if (validateResult.isPresent()) {
            throw new ServiceException(validateResult.get());
        }
    }

    /**
     * 初始化validator 对象
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //获取Hibernate validator 的 validator
        //ValidatorUtils.validator = Validation.buildDefaultValidatorFactory().getValidator();

        //通过Spring 封装的 LocalValidatorFactoryBean获取validator
        ValidatorUtils.validator = (Validator) applicationContext.getBean("validator");
           /*
            @Bean
            public Validator validator() {
                return new LocalValidatorFactoryBean();
             }
            */
    }

    /**
     * 校验分组
     */
    public static class ValidatorGroup {
        public interface First extends Default { }
        public interface Second extends Default { }
        public interface Third extends Default { }
    }

    /**
     * 错误信息封装
     */
    public static class ErrorMessage {
        private String propertyPath;
        private String message;

        public String getPropertyPath() { return propertyPath; }
        public void setPropertyPath(String propertyPath) { this.propertyPath = propertyPath; }

        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
    }
}

使用 ValidatorUtils 校验 bean

java
public void test() {
    // 新增时 id 必须为空
    UserForm addForm = new UserForm();
    addForm.setId("1");
    addForm.setName("张三");
    addForm.setAge(12);
    // 校验 bean 并返回所有验证失败信息
    Optional<String> addResult =  ValidatorUtils.validateResultProcess(addForm, One.class);
    System.out.println(addResult);

    // 更新时id不能为空
    UserForm updateForm = new UserForm();
    updateForm.setId(null);
    addForm.setName("张三");
    updateForm.setAge(12);
    // 校验 bean 并返回所有验证失败信息
    Optional<String> updateResult =  ValidatorUtils.validateResultProcess(updateForm, Two.class);
    System.out.println(updateResult);

}

返回结果

java
Optional[[{"propertyPath":"UserForm.id","message":"新增时id必须为空"}]]
Optional[[{"propertyPath":"UserForm.id","message":"更新时id不能为空"}]]

参数校验常用注解

注解说明
@Null限制只能为 null
@NotNull限制必须不为 null
@NotEmpty验证注解的元素值 不为 null 且不为空字符串长度不为 0、集合大小不为 0)(主要用于:String,Collection,Map,array)
@NotBlank只支持字符串类型字段,验证注解的元素值 不为空不为 null、去除首位空格后长度为 0),不同于 @NotEmpty,@NotBlank 只应用于字符串,且在比较时会去除字符串的空格
@Pattern(value)限制必须符合指定的正则表达式
@Size(max,min)限制字符长度必须在 min 到 max 之间,(主要用于:String,Collection,Map and array)
@Range(min, max)被注释的元素必须在合适的范围内(主要用于:BigDecimal,BigInteger,String,byte,short,int,long,原始类型的包装类)
@Length(min, max)被注解的对象必须是字符串,大小必须在制定的范围内
@DecimalMax(value)限制必须为一个不大于指定值的数字
@DecimalMin(value)限制必须为一个不小于指定值的数字
@Max(value)限制必须为一个不大于指定值的数字
@Min(value)限制必须为一个不小于指定值的数字
@Digits(integer,fraction)限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction
@AssertFalse限制必须为 false
@AssertTrue限制必须为 true
@Future限制必须是一个将来的日期
@Past验证注解的元素值(日期类型)比当前时间早
@Email验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 Email 格式
最近更新